﻿using Microsoft.Extensions.Logging;
using PMS.SANY.MonOrderServer.Models;
using S7.Net;
using S7.Net.Types;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace PMS.SANY.MonOrderServer.Siemens
{
    public class SiemensWriter
    {
        private Plc Client { set; get; }

        public ILogger<Worker> _logger;

        private ConcurrentQueue<SiementsWriteItem> Items { set; get; }

        private ConcurrentDictionary<string,bool> Results { set; get; }

        private SiemensWriter()
        {
        }

        public SiemensWriter(CpuType cpu, string ip, short rack = 0, short slot = 1)
        {
            Client = new Plc(cpu, ip, rack, slot);
            Items = new ConcurrentQueue<SiementsWriteItem>();
            Results = new ConcurrentDictionary<string, bool>();
            _ = AutoWriteAsync();
        }

        public async Task<bool> WriteAsync(string address, VarType type, int count, object value)
        {
            var item = DataItem.FromAddress(address);
            SetDataItemProperty(type, count, value, item);

            var key = Guid.NewGuid().ToString("n");
            Items.Enqueue(new SiementsWriteItem() { Id = key, Item = item });

            try
            {
                if (cancellation != null && !cancellation.IsCancellationRequested)
                {
                    cancellation.Cancel();
                }
            }
            catch
            {
            }

            while (!Results.ContainsKey(key))
            {
                await Task.Delay(1000);
            }

            if (Results.ContainsKey(key))
            {
                bool result = Results[key];
                _ = RemoveResultAsyn(key);
                return result;
            }
            else
            {
                return false;
            }
        }

        private static void SetDataItemProperty(VarType type, int count, object value, DataItem item)
        {
            item.VarType = type;
            item.Count = count;
            switch (type)
            {
                case VarType.Int:
                    item.Value = Convert.ToInt16(value);
                    break;
                case VarType.S7String:
                case VarType.String:
                case VarType.S7WString:
                    item.Value = value.ToString().Trim();
                    break;
                case VarType.Bit:
                    item.Value = Convert.ToBoolean(value);
                    break;
                case VarType.DInt:
                    item.Value = Convert.ToInt32(value);
                    break;
                case VarType.DWord:
                    item.Value = Convert.ToInt32(value);
                    break;
                case VarType.LReal:
                    item.Value = Convert.ToDouble(value);
                    break;
                case VarType.Real:
                    item.Value = Convert.ToSingle(value);
                    break;
                case VarType.Word:
                    item.Value = Convert.ToInt16(value);
                    break;
            }
        }

        private async Task RemoveResultAsyn(string key)
        {
            var id = key;
            bool result;
            while (Results.TryRemove(id, out result))
            {
                await Task.Delay(1000);
            }
        }

        private bool isEnabledWrite = false;
        private async Task AutoWriteAsync()
        {
            if (isEnabledWrite) return;
            else isEnabledWrite = true;

            while (isEnabledWrite)
            {
                SiementsWriteItem data;
                while (Items.TryDequeue(out data))
                {
                    try
                    {
                        if (!Client.IsConnected) await Client.OpenAsync();

                        var item = data.Item;
                        if(item.VarType== VarType.S7String || item.VarType== VarType.S7WString || item.VarType == VarType.String)
                        {
                            Client.Write(item);
                        }
                        else
                        {
                            await Client.WriteAsync(item.DataType, item.DB, item.StartByteAdr, item.Value);
                        }

                        Results.TryAdd(data.Id, true);
                    }
                    catch(Exception ex)
                    {
                        _logger.LogError(ex.Message);
                        Results.TryAdd(data.Id, false);
                    }
                }

                await DelayAsync(15);
            }
        }

        private CancellationTokenSource cancellation;
        /// <summary>
        /// 延时
        /// </summary>
        /// <param name="seconds">单位秒</param>
        /// <returns></returns>
        private async Task DelayAsync(int seconds)
        {
            try
            {
                cancellation = new CancellationTokenSource();
                await Task.Delay(seconds * 1000, cancellation.Token);
            }
            catch
            {
            }
        }
    }
}
